/*
 * 
 *  This file is part of Sourceprojects.org Thracia.
 *  Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.thracia;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.sourceprojects.thracia.output.sender.QueuedReport;
import org.sourceprojects.thracia.output.writer.AbstractOutputWriter;
import org.sourceprojects.thracia.report.beans.Classbase;
import org.sourceprojects.thracia.report.beans.Jvm;
import org.sourceprojects.thracia.report.beans.Property;
import org.sourceprojects.thracia.report.beans.Report;
import org.sourceprojects.thracia.report.beans.Stacktrace;
import org.sourceprojects.thracia.report.beans.Thread;
import org.sourceprojects.thracia.report.beans.Threadgroup;
import org.sourceprojects.thracia.util.ClassbaseHelper;
import org.sourceprojects.thracia.util.SHA1;
import org.sourceprojects.thracia.util.SequenceCounter;

/**
 * @author noctarius
 * 
 */
public class XmlReporter {

	private static final String[] PROPERTIES = { "JAVA_HOME", "CLASSPATH",
			"PATH" };

	private static final String[] JVM_PROPERTIES = { "java.vm.vendor",
			"java.vendor.url", "java.version", "java.vm.name",
			"java.specification.version", "java.class.path", "os.name",
			"os.arch", "os.version" };

	private ReporterConfig config;
	private final Report report;

	public XmlReporter(ReporterConfig config, java.lang.Thread thread,
			java.lang.Throwable exception) {
		this.config = config;

		final Report report = new Report();
		report.setProduct(config.getVersionretriever().getProductName());
		report.setHash(getErrorSha1(exception));
		report.setClassbase(getClassbase());
		report.setRelease(config.getVersionretriever().getVersionNumber());
		report.setDate(getDate());
		report.setMillis(System.currentTimeMillis());
		report.setSequence(SequenceCounter.getInstance().count(
				report.getHash()));
		report.setLogger(config.getLogger().getName());
		report.setTrigger(exception.getStackTrace()[0].getClassName());
		report.setMethod(exception.getStackTrace()[0].getMethodName());
		report.setThreadid(thread.getId());
		report.setPriority(thread.getPriority());
		report.setErrormsg(exception.toString());
		report.setStacktrace(getStacktrace(exception));
		report.setThreads(getThreads(thread));
		report.setJvm(getJvmProperties());

		this.report = report;
	}

	public QueuedReport flush() {
		QueuedReport queue = null;

		try {
			final Class<?> clazz = Class.forName(config.getOutputwriter());
			final Class<?>[] params = { Report.class };
			final Object[] cParams = { report };

			final Constructor<?> constructor = clazz.getConstructor(params);
			final AbstractOutputWriter<?, ?> writer = (AbstractOutputWriter<?, ?>) constructor
					.newInstance(cParams);
			writer.setConfig(config);

			queue = writer.flush();
		} catch (final IllegalAccessException e) {
			e.printStackTrace();
		} catch (final InstantiationException e) {
			e.printStackTrace();
		} catch (final ClassNotFoundException e) {
			e.printStackTrace();
		} catch (final NoSuchMethodException e) {
			e.printStackTrace();
		} catch (final InvocationTargetException e) {
			e.printStackTrace();
		}

		return queue;
	}

	private Classbase getClassbase() {
		final Classbase base = new Classbase();

		base.setChecksum(getClassSha1());
		base.setClassbase(ClassbaseHelper.getClassbase(config.getMainclass()));
		base.setType(ClassbaseHelper.getClassbaseType(config.getMainclass()));

		return base;
	}

	private String getClassSha1() {
		final ClassbaseHelper jar = new ClassbaseHelper(config.getMainclass());
		String checksum;

		try {
			checksum = jar.digest();
		} catch (final IOException e) {
			checksum = e.getMessage();
		}

		return checksum;
	}

	public final ReporterConfig getConfig() {
		return config;
	}

	private String getDate() {
		final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(Calendar.getInstance().getTime());
	}

	private String getErrorSha1(java.lang.Throwable exception) {
		final StackTraceElement element = exception.getStackTrace()[0];
		final String id = element.getClassName() + element.getMethodName()
				+ element.getLineNumber() + exception.getMessage();

		return SHA1.getSha1(id);
	}

	private Jvm getJvmProperties() {
		final Jvm jvm = new Jvm();
		final List<Property> list = new ArrayList<Property>();

		for (final String env : JVM_PROPERTIES) {
			final Property prop = new Property();

			prop.setName(env);
			prop.setValue(java.lang.System.getProperty(env));

			list.add(prop);
		}

		jvm.setProperties(list);
		jvm.setEnvironment(getProperties());

		return jvm;
	}

	private List<Property> getProperties() {
		final List<Property> list = new ArrayList<Property>();

		for (final String env : PROPERTIES) {
			final Property prop = new Property();

			prop.setName(env);
			prop.setValue(java.lang.System.getenv(env));

			list.add(prop);
		}

		return list;
	}

	private List<Stacktrace> getStacktrace(java.lang.Throwable exception) {
		final List<Stacktrace> stacktrace = new ArrayList<Stacktrace>();

		if (exception.getCause() != null) {
			final Stacktrace entry = new Stacktrace();

			entry.setStacktrace(getStacktrace(exception.getCause()));
			stacktrace.add(entry);
		}

		for (final StackTraceElement element : exception.getStackTrace()) {
			final Stacktrace entry = new Stacktrace();

			entry.setClassname(element.getClassName());
			entry.setMethod(element.getMethodName());
			entry.setLine(element.getLineNumber());

			stacktrace.add(entry);
		}

		return stacktrace;
	}

	private Threadgroup getThreadGroup(java.lang.ThreadGroup group) {
		final Threadgroup threadgroup = new Threadgroup();
		threadgroup.setName(group.getName());

		final java.lang.Thread[] threads = new java.lang.Thread[group
				.activeCount() * 2];
		final int numThreads = group.enumerate(threads, false);

		for (int i = 0; i < numThreads; i++) {
			final Thread thread = new Thread();

			thread.setName(threads[i].getName());

			thread.setId(threads[i].getId());
			thread.setStatus(threads[i].getState().name());

			threadgroup.getThreads().add(thread);
		}

		final ThreadGroup[] groups = new ThreadGroup[group.activeGroupCount() * 2];
		final int numGroups = group.enumerate(groups, false);

		for (int i = 0; i < numGroups; i++) {
			final Threadgroup result = getThreadGroup(groups[i]);
			threadgroup.getThreadgroups().add(result);
		}

		return threadgroup;
	}

	private List<Threadgroup> getThreads(java.lang.Thread thread) {
		final List<Threadgroup> threads = new ArrayList<Threadgroup>();

		ThreadGroup root = thread.getThreadGroup().getParent();
		while (root.getParent() != null) {
			root = root.getParent();
		}

		final Threadgroup result = getThreadGroup(root);
		threads.add(result);

		return threads;
	}

	public final void setConfig(ReporterConfig config) {
		this.config = config;
	}
}
